"""
@author: 江同学呀
@file: functions.py
@date: 2025/1/13 20:51
@desc: 
"""
from typing import Literal, Union, Dict, List

from espc.common.common import Number
from espc.common.query_common import MultiValueMode
from espc.orm.model.base.base import _Base
from espc.orm.model.mapping.field.base_field.base_field import _BaseField


class DecayFunctions(_Base):
    """
    衰减函数

    Decay functions score a document with a function that decays depending on the distance of a numeric field value of
    the document from a user given origin. This is similar to a range query, but with smooth edges instead of boxes.
    To use distance scoring on a query that has numerical fields, the user has to define an origin and a scale for each
    field. The origin is needed to define the “central point” from which the distance is calculated, and the scale to
    define the rate of decay. The decay function is specified as
    "DECAY_FUNCTION": {
        "FIELD_NAME": {
              "origin": "11, 12",
              "scale": "2km",
              "offset": "0km",
              "decay": 0.33
        }
    }

    衰减函数使用一个函数对文档进行评分，该函数根据文档的数值字段值与用户给定源的距离进行衰减。这类似于范围查询，但具有平滑的边缘而不是框。
    要对具有数字字段的查询使用距离评分，用户必须为每个字段定义原点和小数位数。需要原点来定义计算距离的“中心点”，需要比例来定义衰减速率。decay 函数指定为
    "DECAY_FUNCTION": {
        "FIELD_NAME": {
              "origin": "11, 12",
              "scale": "2km",
              "offset": "0km",
              "decay": 0.33
        }
    }

    :param origin:
        The point of origin used for calculating distance. Must be given as a number for numeric field, date for date
        fields and geo point for geo fields. Required for geo and numeric field. For date fields the default is now.
        Date math (for example now-1h) is supported for origin.
        用于计算距离的原点。必须以数字字段的数字形式提供，日期字段的日期必须以数字形式提供，地理位置字段的 geo point 必须以数字形式提供。
        对于 geo 和 numeric 字段是必需的。对于日期字段，默认值为 now。origin 支持日期数学（例如 now-1h）。
    :param scale:
        Required for all types. Defines the distance from origin + offset at which the computed score will equal decay
        parameter. For geo fields: Can be defined as number+unit (1km, 12m,…​). Default unit is meters. For date
        fields: Can to be defined as a number+unit ("1h", "10d",…​). Default unit is milliseconds. For numeric
        field: Any number.
        对于所有类型都是必需的。定义从原点 + 偏移的距离，计算的分数将等于衰减参数。对于 geo 字段：可以定义为数字 + 单位（1 公里、12 米,...）。
        默认单位为米。对于日期字段：可以定义为数字 + 单位（“1h”、“10d”,...）。默认单位为毫秒。对于数值字段：任意数字。
    :param offset:
        If an offset is defined, the decay function will only compute the decay function for documents with a distance
        greater than the defined offset. The default is 0.
        如果定义了偏移量，则 decay 函数将仅计算距离大于定义偏移量的文档的衰减函数。默认值为 0。
    :param decay:
        The decay parameter defines how documents are scored at the distance given at scale. If no decay is defined,
        documents at the distance scale will be scored 0.5.
        decay 参数定义如何在比例给定的距离处对文档进行评分。如果未定义衰减，则距离刻度的文档将得分为 0.5。
    """

    def __init__(
            self, func_name: Literal["gauss", "exp", "linear"], field: Union[str, _BaseField],
            origin: Union[str, Number], scale: Union[str, Number], offset: Union[str, Number] = None,
            decay: Number = None, multi_value_mode: Union[Literal["min", "max", "avg", "sum"], MultiValueMode] = None
    ):
        super().__init__()
        self._func_name: Literal["gauss", "exp", "linear"] = func_name
        self._field: Union[str, _BaseField] = field
        self._origin: Union[str, Number] = origin
        self._scale: Union[str, Number] = scale
        self._offset: Union[str, Number, None] = offset
        self._decay: Number = decay
        self._multi_value_mode: Union[Literal["min", "max", "avg", "sum"], MultiValueMode, None] = multi_value_mode
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "origin": self._origin,
            "scale": self._scale,
        }
        if self._offset is not None:
            _body["offset"] = self._offset
        if self._decay is not None:
            _body["decay"] = self._decay
        return {
            (self._field if isinstance(self._field, str) else self._field._field_name): _body,
            "multi_value_mode": (
                self._multi_value_mode if isinstance(self._multi_value_mode, str) else self._multi_value_mode.value
            )
        }


class Saturation(_Base):
    """
    饱和度函数
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-rank-feature-query.html#rank-feature-query-saturation

    The saturation function gives a score equal to S / (S + pivot), where S is the value of the rank feature field and
    pivot is a configurable pivot value so that the result will be less than 0.5 if S is less than pivot and greater
    than 0.5 otherwise. Scores are always (0,1).
    If the rank feature has a negative score impact then the function will be computed as pivot / (S + pivot), which
    decreases when S increases.

    饱和度函数给出的分数等于 S / （S + pivot），其中 S 是排名特征字段的值，pivot 是可配置的枢轴值，因此如果 S 小于 pivot，则结果将
    小于 0.5，否则结果将大于 0.5。分数始终为 （0,1）。
    如果排名功能对分数有负面影响，则该函数将计算为 pivot / （S + pivot），当 S 增加时，该函数会减小。

    :param pivot:
    """
    type: str = "saturation"

    def __init__(self, pivot: Number = None):
        super().__init__()
        self._pivot: Number = pivot
        return

    def _build(self) -> Dict:
        _body: Dict = {}
        if self._pivot is not None:
            _body["pivot"] = self._pivot
        return _body


class Logarithm(_Base):
    """
    对数函数
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-rank-feature-query.html#rank-feature-query-logarithm

    The log function gives a score equal to log(scaling_factor + S), where S is the value of the rank feature field and
    scaling_factor is a configurable scaling factor. Scores are unbounded.
    This function only supports rank features that have a positive score impact.

    log 函数给出的分数等于 log（scaling_factor + S），其中 S 是排名特征字段的值，scaling_factor 是可配置的缩放因子。分数是无限的。
    此函数仅支持对分数有积极影响的排名功能。

    :param scaling_factor:
    """
    type: str = "log"

    def __init__(self, scaling_factor: Number = None):
        super().__init__()
        self._scaling_factor: Number = scaling_factor
        return

    def _build(self) -> Dict:
        _body: Dict = {}
        if self._scaling_factor is not None:
            _body["scaling_factor"] = self._scaling_factor
        return _body


class Sigmoid(_Base):
    """
    Sigmoid 函数
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-rank-feature-query.html#rank-feature-query-sigmoid

    The sigmoid function is an extension of saturation which adds a configurable exponent. Scores are computed as
    S^exp^ / (S^exp^ + pivot^exp^). Like for the saturation function, pivot is the value of S that gives a score of
    0.5 and scores are (0,1).
    The exponent must be positive and is typically in [0.5, 1]. A good value should be computed via training. If you
    don’t have the opportunity to do so, we recommend you use the saturation function instead.

    sigmoid 函数是饱和度的扩展，它增加了一个可配置的指数。分数计算为 S^exp^ / （S^exp^ + pivot^exp^）。与饱和度函数一样，pivot 是 S 的
    值，分数为 0.5，分数为 （0,1）。
    指数必须为正数，通常在 [0.5， 1] 中。应该通过训练来计算一个好的值。如果您没有机会这样做，我们建议您改用 saturation 函数。
    :param pivot:
    :param exponent:
    """
    type: str = "sigmoid"

    def __init__(self, pivot: Number = None, exponent: Number = None):
        super().__init__()
        self._pivot: Number = pivot
        self._exponent: Number = exponent
        return

    def _build(self) -> Dict:
        _body: Dict = {}
        if self._pivot is not None:
            _body["pivot"] = self._pivot
        if self._exponent is not None:
            _body["exponent"] = self._exponent
        return _body


class Linear(_Base):
    """
    线性函数
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-rank-feature-query.html#rank-feature-query-linear

    The linear function is the simplest function, and gives a score equal to the indexed value of S, where S is the
    value of the rank feature field. If a rank feature field is indexed with "positive_score_impact": true, its indexed
    value is equal to S and rounded to preserve only 9 significant bits for the precision. If a rank feature field is
    indexed with "positive_score_impact": false, its indexed value is equal to 1/S and rounded to preserve only 9
    significant bits for the precision.

    线性函数是最简单的函数，它给出的分数等于 S 的索引值，其中 S 是 rank 特征字段的值。如果使用 “positive_score_impact”： true 为排名特
    征字段编制索引，则其索引值等于 S 并四舍五入以仅保留 9 个有效位用于精度。如果使用 “positive_score_impact”： false 对排名特征字段进行
    索引，则其索引值等于 1/S 并四舍五入以仅保留 9 个有效位的精度。

    :param positive_score_impact:
    """
    type: str = "linear"

    def __init__(self, positive_score_impact: bool = None):
        super().__init__()
        self._positive_score_impact: bool = positive_score_impact
        return

    def _build(self) -> Dict:
        _body: Dict = {}
        if self._positive_score_impact is not None:
            _body["positive_score_impact"] = self._positive_score_impact
        return _body


class Indicator(_Base):
    """

    :param doc_count:
        (Required, integer) The total number of documents that initially created the expectations. It’s required to be
        greater than or equal to the sum of all values in the buckets_path as this is the originating superset of data
        to which the term values are correlated.
        （必需，整数）最初创建预期的文档总数。它必须大于或等于 buckets_path 中所有值的总和，因为这是与术语值相关的原始数据超集。
    :param expectations:
        (Required, array) An array of numbers with which to correlate the configured bucket_path values. The length of
        this value must always equal the number of buckets returned by the bucket_path.
        （必需，数组）一个数字数组，用于将配置的 bucket_path 值相关联。此值的长度必须始终等于 bucket_path 返回的存储桶数。
    :param fractions:
        (Optional, array) An array of fractions to use when averaging and calculating variance. This should be used if
        the pre-calculated data and the buckets_path have known gaps. The length of fractions, if provided, must equal
        expectations.
        （可选，数组）求平均值和计算方差时使用的分数数组。如果预先计算的数据和buckets_path具有已知的差距，则应使用此参数。分数的长度
        （如果提供）必须等于预期。
    """
    def __init__(self, doc_count: int, expectations: List[Number], fractions: List[Number] = None):
        super().__init__()
        self._doc_count: int = doc_count
        self._expectations: List[Number] = expectations
        self._fractions: List[Number] = fractions
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "doc_count": self._doc_count,
            "expectations": self._expectations
        }
        if self._fractions is not None:
            _body["fractions"] = self._fractions
        return _body


class CountCorrelation(_Base):
    """

    :param indicator:
        (Required, object) The indicator with which to correlate the configured bucket_path values.
        （必需，对象）将配置的 bucket_path 值与之关联的指示器。
    :param doc_count:
        (Required, integer) The total number of documents that initially created the expectations. It’s required to be
        greater than or equal to the sum of all values in the buckets_path as this is the originating superset of data
        to which the term values are correlated.
        （必需，整数）最初创建预期的文档总数。它必须大于或等于 buckets_path 中所有值的总和，因为这是与术语值相关的原始数据超集。
    :param expectations:
        (Required, array) An array of numbers with which to correlate the configured bucket_path values. The length of
        this value must always equal the number of buckets returned by the bucket_path.
        （必需，数组）一个数字数组，用于将配置的 bucket_path 值相关联。此值的长度必须始终等于 bucket_path 返回的存储桶数。
    :param fractions:
        (Optional, array) An array of fractions to use when averaging and calculating variance. This should be used if
        the pre-calculated data and the buckets_path have known gaps. The length of fractions, if provided, must equal
        expectations.
        （可选，数组）求平均值和计算方差时使用的分数数组。如果预先计算的数据和buckets_path具有已知的差距，则应使用此参数。分数的长度
        （如果提供）必须等于预期。
    """
    def __init__(
            self, indicator: Union[Dict, Indicator] = None, doc_count: int = None, expectations: List[Number] = None,
            fractions: List[Number] = None
    ):
        super().__init__()
        if indicator:
            self._indicator: Union[Dict, Indicator] = indicator
        else:
            self._indicator: Indicator = Indicator(doc_count=doc_count, expectations=expectations, fractions=fractions)
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "indicator": self._indicator if isinstance(self._indicator, Dict) else self._indicator._build()
        }
        return _body


class BucketCorrelationFunction(_Base):
    """

    :param count_correlation:
        (Required*, object) The configuration to calculate a count correlation. This function is designed for
        determining the correlation of a term value and a given metric. Consequently, it needs to meet the following
        requirements.
            - The buckets_path must point to a _count metric.
            - The total count of all the bucket_path count values must be less than or equal to indicator.doc_count.
            - When utilizing this function, an initial calculation to gather the required indicator values is required.

        （必需*，对象）用于计算计数相关性的配置。此函数旨在确定术语值与给定量度的相关性。因此，它需要满足以下要求。
            - buckets_path 必须指向 _count 指标。
            - 所有 bucket_path count 值的总数必须小于或等于 indicator.doc_count。
            - 使用此功能时，需要进行初始计算以收集所需的指标值。
    :param indicator:
        (Required, object) The indicator with which to correlate the configured bucket_path values.
        （必需，对象）将配置的 bucket_path 值与之关联的指示器。
    :param doc_count:
        (Required, integer) The total number of documents that initially created the expectations. It’s required to be
        greater than or equal to the sum of all values in the buckets_path as this is the originating superset of data
        to which the term values are correlated.
        （必需，整数）最初创建预期的文档总数。它必须大于或等于 buckets_path 中所有值的总和，因为这是与术语值相关的原始数据超集。
    :param expectations:
        (Required, array) An array of numbers with which to correlate the configured bucket_path values. The length of
        this value must always equal the number of buckets returned by the bucket_path.
        （必需，数组）一个数字数组，用于将配置的 bucket_path 值相关联。此值的长度必须始终等于 bucket_path 返回的存储桶数。
    :param fractions:
        (Optional, array) An array of fractions to use when averaging and calculating variance. This should be used if
        the pre-calculated data and the buckets_path have known gaps. The length of fractions, if provided, must equal
        expectations.
        （可选，数组）求平均值和计算方差时使用的分数数组。如果预先计算的数据和buckets_path具有已知的差距，则应使用此参数。分数的长度
        （如果提供）必须等于预期。
    """

    def __init__(
            self, count_correlation: Union[Dict, CountCorrelation] = None, indicator: Union[Dict, Indicator] = None,
            doc_count: int = None, expectations: List[Number] = None, fractions: List[Number] = None
    ):
        super().__init__()
        if count_correlation:
            self._count_correlation: Union[Dict, CountCorrelation] = count_correlation
        else:
            self._count_correlation: CountCorrelation = CountCorrelation(
                indicator=indicator, doc_count=doc_count, expectations=expectations, fractions=fractions
            )
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "count_correlation":
                self._count_correlation
                if isinstance(self._count_correlation, Dict)
                else self._count_correlation._build()
        }
        return _body
